package cn.congine.wizarpos.mall.tenpay;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.codec.binary.Hex;

import cn.congine.wizarpos.mall.exception.MangoException;
import cn.congine.wizarpos.mall.model.WxOrder;
import cn.congine.wizarpos.mall.model.WxOrderDetail;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

public class PaymentMessage {
	@JsonProperty("appId")
	private String appId;

	@JsonIgnore
	private String appKey;

	@JsonProperty("timeStamp")
	private String timestamp;

	@JsonProperty("nonceStr")
	private String noncestr;

	@JsonProperty("paymentPackage")
	private String paymentPackage;

	@JsonProperty("signType")
	private String signType = "SHA1";

	@JsonProperty("paySign")
	private String sign;

	public String getAppId() {
		return appId;
	}

	public void setAppId(String appId) {
		this.appId = appId;
	}

	public String getAppKey() {
		return appKey;
	}

	public void setAppKey(String appKey) {
		this.appKey = appKey;
	}

	public String getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(String timestamp) {
		this.timestamp = timestamp;
	}

	public String getNoncestr() {
		return noncestr;
	}

	public void setNoncestr(String noncestr) {
		this.noncestr = noncestr;
	}

	public String getPaymentPackage() {
		return paymentPackage;
	}

	public void setPaymentPackage(String paymentPackage) {
		this.paymentPackage = paymentPackage;
	}

	public String getSignType() {
		return signType;
	}

	public void setSignType(String signType) {
		this.signType = signType;
	}

	public String getSign() {
		return sign;
	}

	public void setSign(String sign) {
		this.sign = sign;
	}

	public void setPackage(String partnerId, String partnerKey,
			String notifyUrl, String clientIP, WxOrder order,
			List<WxOrderDetail> orderItems) throws MangoException {
		PaymentPackage pkg = new PaymentPackage();
		pkg.setPartner(partnerId);
		pkg.setNotifyUrl(notifyUrl);
		pkg.setClientIp(clientIP);
		pkg.assemble(order, orderItems);
		setPaymentPackage(pkg.getPackage(partnerKey));
	}

	public void sign(String appId, String appKey) {
		setAppId(appId);
		setAppKey(appKey);

		setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));
		setNoncestr(getRadom());

		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", getAppId());
		map.put("timestamp", getTimestamp());
		map.put("noncestr", getNoncestr());
		map.put("package", getPaymentPackage());
		map.put("appkey", getAppKey());

		StringBuffer sb = new StringBuffer();
		SortedSet<String> keys = new TreeSet<String>(map.keySet());
		Iterator<String> it = keys.iterator();
		while (it.hasNext()) {
			String key = it.next();
			String value = map.get(key);
			sb.append(key).append("=").append(value);
			if (it.hasNext()) {
				sb.append("&");
			}
		}
		try {
			MessageDigest sha = MessageDigest.getInstance("SHA1");
			sha.update(sb.toString().getBytes("UTF-8"));
			byte[] array = sha.digest();
			setSign(new String(Hex.encodeHex(array)));
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
		}
	}

	public static class PaymentPackage {
		private String bankType = "WX";

		private String body;

		private String attach = null;

		private String partner;

		private String tradeNo;

		private Integer totalFee;

		private BigDecimal transportFee = null;

		private BigDecimal productFee = null;

		private String feeType = "1";

		private String notifyUrl;

		private String clientIp;

		private Date start = null;

		private Date expire = null;

		private String goodsTag = null;

		private String charset = "UTF-8";

		public String getBankType() {
			return bankType;
		}

		public void setBankType(String bankType) {
			this.bankType = bankType;
		}

		public String getBody() {
			return body;
		}

		public void setBody(String body) {
			this.body = body;
		}

		public String getAttach() {
			return attach;
		}

		public void setAttach(String attach) {
			this.attach = attach;
		}

		public String getPartner() {
			return partner;
		}

		public void setPartner(String partner) {
			this.partner = partner;
		}

		public String getTradeNo() {
			return tradeNo;
		}

		public void setTradeNo(String tradeNo) {
			this.tradeNo = tradeNo;
		}

		public Integer getTotalFee() {
			return totalFee;
		}

		public void setTotalFee(Integer totalFee) {
			this.totalFee = totalFee;
		}

		public BigDecimal getTransportFee() {
			return transportFee;
		}

		public void setTransportFee(BigDecimal transportFee) {
			this.transportFee = transportFee;
		}

		public BigDecimal getProductFee() {
			return productFee;
		}

		public void setProductFee(BigDecimal productFee) {
			this.productFee = productFee;
		}

		public String getFeeType() {
			return feeType;
		}

		public void setFeeType(String feeType) {
			this.feeType = feeType;
		}

		public String getNotifyUrl() {
			return notifyUrl;
		}

		public void setNotifyUrl(String notifyUrl) {
			this.notifyUrl = notifyUrl;
		}

		public String getClientIp() {
			return clientIp;
		}

		public void setClientIp(String clientIp) {
			this.clientIp = clientIp;
		}

		public Date getStart() {
			return start;
		}

		public void setStart(Date start) {
			this.start = start;
		}

		public Date getExpire() {
			return expire;
		}

		public void setExpire(Date expire) {
			this.expire = expire;
		}

		public String getGoodsTag() {
			return goodsTag;
		}

		public void setGoodsTag(String goodsTag) {
			this.goodsTag = goodsTag;
		}

		public String getCharset() {
			return charset;
		}

		public void setCharset(String charset) {
			this.charset = charset;
		}

		public String getPackage(String paternerKey) {
			String sign = null;
			try {
				String msg = toString(false) + "&key=" + paternerKey;
				MessageDigest md = MessageDigest.getInstance("MD5");
				byte[] array = md.digest(msg.getBytes("UTF-8"));
				sign = new String(Hex.encodeHex(array));
				sign = sign.toUpperCase();
			} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
				return null;
			}
			return toString(true) + "&sign=" + sign;
		}

		public void assemble(WxOrder order, List<WxOrderDetail> orderItems)
				throws MangoException {
			StringBuffer _body = new StringBuffer();
			Iterator<WxOrderDetail> it = orderItems.iterator();
			while (it.hasNext()) {
				_body.append(it.next().getProductName());
				if (it.hasNext()) {
					_body.append(",");
				}
			}
			setBody(_body.toString());
			setTradeNo(order.getOrderId());
			setTotalFee(order.getAmount());
		}

		private String toString(boolean urlEncode) {
			StringBuffer sb = new StringBuffer();
			Map<String, String> data = getData();
			SortedSet<String> keys = new TreeSet<String>(data.keySet());
			Iterator<String> it = keys.iterator();
			while (it.hasNext()) {
				String key = it.next();
				String value = data.get(key);
				sb.append(key).append("=");
				if (urlEncode) {
					sb.append(urlEncode(value));
				} else {
					sb.append(value);
				}
				if (it.hasNext()) {
					sb.append("&");
				}
			}
			return sb.toString();
		}

		private Map<String, String> getData() {
			Map<String, String> map = new HashMap<String, String>();
			map.put("bank_type", getBankType());
			map.put("body", getBody());
			if (!isEmpty(getAttach())) {
				map.put("attach", getAttach());
			}
			map.put("partner", getPartner());
			map.put("out_trade_no", getTradeNo());
			map.put("fee_type", getFeeType());
			map.put("notify_url", getNotifyUrl());
			map.put("spbill_create_ip", getClientIp());
			if (getStart() != null) {
				SimpleDateFormat stf = new SimpleDateFormat("yyyyMMddHHmmss");
				map.put("time_start", stf.format(getStart()));
			}
			if (getExpire() != null) {
				SimpleDateFormat stf = new SimpleDateFormat("yyyyMMddHHmmss");
				map.put("time_expire", stf.format(getExpire()));
			}
			if (!isEmpty(getGoodsTag())) {
				map.put("goods_tag", getGoodsTag());
			}
			map.put("input_charset", getCharset());
			map.put("total_fee", getTotalFee().toString());
			if (getTransportFee() != null) {
				map.put("transport_fee", Long.toString(WeChatUtility
						.toWechatPrice(getTransportFee())));
			}
			if (getProductFee() != null) {
				map.put("product_fee", Long.toString(WeChatUtility
						.toWechatPrice(getProductFee())));
			}
			return map;
		}

		private String urlEncode(String src) {
			try {
				return URLEncoder.encode(src, "UTF-8").replace("+", "%20");
			} catch (UnsupportedEncodingException e) {
				return src;
			}
		}
	}

	private String getRadom() {
		Random random = new Random();
		String _rand = String.valueOf(random.nextInt(10000));
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");
			byte[] array = md.digest(_rand.getBytes());
			return new String(Hex.encodeHex(array));
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
	}

	public static boolean isEmpty(String str) {
		return ((str == null) || (str.length() == 0) || (str.trim().length() == 0));
	}
}
